Skip to content

feat: add message reactions#98

Merged
shakkernerd merged 8 commits into
openclaw:mainfrom
jjjhenriksen:codex/message-reactions-submit
Jul 20, 2026
Merged

feat: add message reactions#98
shakkernerd merged 8 commits into
openclaw:mainfrom
jjjhenriksen:codex/message-reactions-submit

Conversation

@jjjhenriksen

@jjjhenriksen jjjhenriksen commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Summary

  • add accessible optimistic emoji reactions to message timelines, thread roots, thread replies, channel embeds, and thread embeds
  • expose per-emoji reaction counts and the current viewer's state without returning individual reacting users
  • return authoritative reaction summaries from mutations and apply ordered realtime counts without refetching messages
  • add TypeScript SDK methods, OpenAPI coverage, feature docs, and a reproducible real-app proof

Implementation

  • hydrates channel, direct-message, single-message, nonce, and thread reads with one aggregated reaction query per message window
  • serializes PostgreSQL reaction writers per message so emitted absolute counts remain ordered under concurrency
  • centralizes frontend reaction state in a bounded view controller that protects newer realtime state from stale page and mutation responses
  • preserves optimistic rollback and ambiguous-failure recovery while keeping unrelated concurrent reactions intact
  • rebuilds the checked-in production web bundle used by the Go binary

Coverage

  • SQLite and PostgreSQL aggregation, viewer state, nonce reads, thread windows, and concurrent PostgreSQL writers
  • HTTP mutation response shape and no-op behavior
  • persistence, accessibility, optimistic failure, delayed responses, stale pagination, and no-GET realtime reconciliation
  • main timeline, thread root/reply, channel embed, and thread embed flows

Verification

  • pnpm generate:sqlc
  • pnpm --filter @clickclack/protocol generate
  • go build ./apps/api/cmd/clickclack
  • pnpm build
  • pnpm fmt:check
  • pnpm lint
  • pnpm typecheck
  • pnpm -r typecheck
  • git diff --check

The real-app screenshot and reproduction command are documented in docs/proof/message-reactions-proof.md.

@clawsweeper clawsweeper Bot added proof: sufficient Contributor real behavior proof is sufficient. proof: 📸 screenshot Contributor real behavior proof includes screenshot evidence. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. P2 Normal priority bug or improvement with limited blast radius. labels Jul 18, 2026
@clawsweeper

clawsweeper Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs changes before merge. Reviewed July 18, 2026, 9:53 PM ET / July 19, 2026, 01:53 UTC.

Summary
The PR adds persisted per-user emoji reactions, realtime reconciliation, optimistic controls, generated web assets, and browser/store coverage for ClickClack messages.

Reproducibility: not applicable. as a bug report: this PR proposes a new capability. The contributor provides a high-confidence real-app execution path for the proposed behavior, but that does not replace the missing thread-path coverage.

Review metrics: 2 noteworthy metrics.

  • Cross-stack scope: 54 files affected; 1,475 added and 126 removed. The feature changes storage, API responses, realtime reconciliation, web UI, generated assets, and browser coverage, so a focused completion pass is warranted.
  • Focused browser coverage: 1 new Playwright spec with 10 passing scenarios. The supplied proof covers rendered UI behavior, persistence, realtime updates, and several optimistic-state races.

Merge readiness
Overall: 🦐 gold shrimp
Proof: 🦞 diamond lobster ✨ media proof bonus
Patch quality: 🦐 gold shrimp
Result: needs maintainer review before merge.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Rank-up moves:

  • [P2] Add ReactionsBar or an equivalent shared reaction control to thread root and reply rendering.
  • [P2] Add a targeted Playwright assertion that adding or removing a thread reaction is visible and survives realtime reconciliation.
  • Provide focused existing-installation upgrade/migration evidence for the persisted reaction data.

Mantis proof suggestion
A short redacted web-chat interaction can directly demonstrate the missing or repaired thread reaction controls alongside the existing main-timeline behavior. A maintainer can ask Mantis to capture proof by posting this exact PR comment:

@openclaw-mantis web UI chat proof: verify and capture that reactions can be added and removed on both a channel message and a thread reply, with the updated count visible in each view.

Risk before merge

  • [P1] Reaction persistence changes stored and transported message data; the supplied proof demonstrates fresh real-app behavior but does not clearly establish upgrade-migration behavior for existing installations.
  • [P1] Accepting reactions establishes a permanent core chat interaction and API/data-model surface, so feature scope should be explicitly sponsored before merge.

Maintainer options:

  1. Complete threads and prove upgrades (recommended)
    Render and mutate reactions in the thread panel and add a focused existing-database migration/upgrade check before considering merge.
  2. Accept scoped upgrade uncertainty
    A maintainer may accept the current upgrade evidence only after explicitly confirming the migration and additive message response are safe for existing deployments.

Next step before merge

  • [P2] A narrow mechanical repair can add the existing reaction surface to the thread rendering path and extend the focused browser proof, while the separate core-scope decision remains with a maintainer.

Maintainer decision needed

  • Question: Should ClickClack adopt persisted message reactions as a permanent core interaction after the thread-rendering and upgrade-proof gaps are resolved?
  • Rationale: The patch is a substantial new user-facing interaction across API, storage, realtime, and web UI; implementation quality cannot determine whether this capability belongs in the core product.
  • Likely owner: Unattributed current-main history — Current-main ownership could not be established from the supplied metadata, so a product owner for the chat surface should make the scope decision.
  • Options:
    • Sponsor core reactions (recommended): Accept the feature direction, require the thread rendering repair and upgrade evidence, then review the completed implementation for merge.
    • Defer core adoption: Keep the PR open only if maintainers want a narrower alternative or a future product plan for reactions; otherwise decline the direction without treating the implementation as a bug fix.

Security
Cleared: The supplied diff adds no dependency source, workflow permission, secret-handling, package-publishing, or external code-execution change with a concrete security or supply-chain concern.

Review findings

  • [P2] Render reactions in thread messages — apps/web/src/components/messages/MessageRow.svelte:131-137
Review details

Best possible solution:

Render and exercise reactions in both the main timeline and thread panel, add focused upgrade-path evidence for persisted reaction data, then land only if a maintainer sponsors reactions as a core ClickClack interaction.

Do we have a high-confidence way to reproduce the issue?

Not applicable as a bug report: this PR proposes a new capability. The contributor provides a high-confidence real-app execution path for the proposed behavior, but that does not replace the missing thread-path coverage.

Is this the best way to solve the issue?

No. The architecture and proof are substantial, but rendering controls solely through MessageRow leaves thread roots and replies without the proposed capability; the narrower maintainable fix is to share the reaction surface with ThreadPanel and prove upgrade behavior.

Full review comments:

  • [P2] Render reactions in thread messages — apps/web/src/components/messages/MessageRow.svelte:131-137
    The current patch wires ReactionsBar into MessageRow, but the supplied exact-head review establishes that ThreadPanel.svelte renders thread roots and replies directly. Reactions are therefore hydrated and reconciled for thread messages without becoming visible or actionable there; add the shared reaction UI to that rendering path and cover it in the browser test.
    Confidence: 0.98

Overall correctness: patch is incorrect
Overall confidence: 0.98

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against 146f76255bcc.

Label changes

Label changes:

  • add merge-risk: 🚨 compatibility: The PR introduces persisted reaction data and extends message transport fields, so existing-installation migration behavior needs explicit confirmation.

Label justifications:

  • P2: This is a useful new interaction with a concrete thread usability defect, but it is not an urgent regression or availability/security incident.
  • merge-risk: 🚨 compatibility: The PR introduces persisted reaction data and extends message transport fields, so existing-installation migration behavior needs explicit confirmation.
  • rating: 🦐 gold shrimp: Overall readiness is 🦐 gold shrimp; proof is 🦞 diamond lobster and patch quality is 🦐 gold shrimp.
  • status: ⏳ waiting on author: ClawSweeper has contributor-facing work open and is waiting for author action. Sufficient (screenshot): The PR includes committed screenshot proof plus a described after-fix Playwright run against the locally built production web bundle and real Go API, covering visible persistence and realtime behavior.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR includes committed screenshot proof plus a described after-fix Playwright run against the locally built production web bundle and real Go API, covering visible persistence and realtime behavior.
  • proof: 📸 screenshot: Contributor real behavior proof includes screenshot evidence. The PR includes committed screenshot proof plus a described after-fix Playwright run against the locally built production web bundle and real Go API, covering visible persistence and realtime behavior.
Evidence reviewed

Acceptance criteria:

  • [P1] pnpm check.
  • [P1] pnpm exec playwright test tests/e2e/message-reactions.spec.ts.
  • [P1] go test ./apps/api/internal/store/sqlite ./apps/api/internal/store/postgres -run '^TestGetThreadHydratesReactions$' -count=1.

What I checked:

  • Unresolved thread rendering defect: The branch adds ReactionsBar only through MessageRow, while the supplied exact-head review confirms ThreadPanel renders thread roots and replies directly; thread reactions therefore remain invisible and cannot be toggled. (apps/web/src/components/messages/MessageRow.svelte:131, abfeb871fc30)
  • Prior exact-head review remains applicable: The previous ClawSweeper review against this same head SHA reported “Render reactions in thread messages”; no ThreadPanel change is present in the supplied 54-file diff. (apps/web/src/components/messages/MessageRow.svelte:131, abfeb871fc30)
  • Real behavior proof: The PR provides a committed real-app screenshot and describes a focused Playwright run against a locally built production bundle served by the real Go API, including persistence and realtime reconciliation scenarios. (docs/proof/message-reactions-proof.md:1, abfeb871fc30)
  • Repository SQL policy followed: The PR updates sqlc query sources and corresponding generated SQLite/PostgreSQL store code rather than hand-editing generated code alone. (apps/api/internal/store/sqlite/sqlc/queries.sql:1206, abfeb871fc30)
  • Automated validation: All seven reported checks succeeded, including Go, TypeScript, Playwright E2E, Docker Image, and the three desktop-platform jobs. (abfeb871fc30)

Likely related people:

  • Unattributed current-main history: The supplied review context identifies no prior current-main author for the affected chat and store surfaces, so ownership cannot be attributed confidently without the unavailable local history inspection. (role: likely follow-up owner; confidence: low; files: apps/web/src/components/messages/MessageRow.svelte, apps/web/src/components/messages/ThreadPanel.svelte, apps/api/internal/store/sqlite/message_pages.go)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.
Review history (3 earlier review cycles)
  • reviewed 2026-07-18T23:14:46.226Z sha 61a1869 :: needs maintainer review before merge. :: none
  • reviewed 2026-07-19T00:39:18.832Z sha 61a1869 :: needs maintainer review before merge. :: none
  • reviewed 2026-07-19T01:11:52.551Z sha abfeb87 :: found issues before merge. :: [P2] Render reactions in thread messages

@jjjhenriksen
jjjhenriksen marked this pull request as ready for review July 19, 2026 00:36

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 61a1869d66

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +135 to +139
<ReactionsBar
messageId={message.id}
reactions={message.reactions ?? []}
{currentUserID}
/>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Render reactions inside threads

This only wires ReactionsBar into the main timeline rows; the thread pane renders its root and replies directly in ThreadPanel.svelte and never includes the bar. As a result, reactions that are now hydrated and realtime-updated for selectedThread/replies are invisible and not toggleable for thread replies, which do not have a separate main-timeline row where the user could see them.

Useful? React with 👍 / 👎.

body: JSON.stringify({ emoji }),
});
}
const data = await api<{ message: { reactions?: Reaction[] } }>(`/api/messages/${messageId}`);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Don't treat refresh failures as reaction failures

When the POST/DELETE has already succeeded but this follow-up GET /api/messages/:id fails transiently, execution falls into the same catch as a failed mutation and may roll back the optimistic add/remove even though the server committed it. This leaves the current row showing an error and the opposite reaction state until a later realtime/reload corrects it; separate mutation failures from reconciliation-refresh failures or keep the committed optimistic state when only the refresh fails.

Useful? React with 👍 / 👎.

Comment thread apps/web/src/ChatApp.svelte Outdated
messages.some((message) => message.id === messageID) ||
replies.some((reply) => reply.id === messageID) ||
selectedThread?.id === messageID;
if (!isLoaded) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve reaction snapshots during in-flight reloads

When the reaction is for a message that is already loaded, this skips recording a pending snapshot and only mutates the current arrays. If a message-page or thread refresh that read data before the reaction resolves afterward, its stale response can overwrite the updated reactions because applyPendingReactionSnapshots has nothing to apply. Store the snapshot for loaded messages too, or version those reloads, so in-flight refreshes cannot revert the realtime reaction state.

Useful? React with 👍 / 👎.

@jjjhenriksen
jjjhenriksen force-pushed the codex/message-reactions-submit branch from 61a1869 to abfeb87 Compare July 19, 2026 01:08
@jjjhenriksen

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

Re-review progress:

@clawsweeper clawsweeper Bot added rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. merge-risk: 🚨 compatibility 🚨 Merging this PR could break existing users, config, migrations, defaults, or upgrades. and removed rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. labels Jul 19, 2026
@shakkernerd shakkernerd self-assigned this Jul 20, 2026
shakkernerd added a commit to jjjhenriksen/clickclack that referenced this pull request Jul 20, 2026
@shakkernerd
shakkernerd force-pushed the codex/message-reactions-submit branch from abfeb87 to 8dbeef6 Compare July 20, 2026 20:39
shakkernerd added a commit to jjjhenriksen/clickclack that referenced this pull request Jul 20, 2026
@shakkernerd
shakkernerd force-pushed the codex/message-reactions-submit branch from 8dbeef6 to 07ce90f Compare July 20, 2026 20:41
@shakkernerd
shakkernerd force-pushed the codex/message-reactions-submit branch from 07ce90f to ad53543 Compare July 20, 2026 20:50
@shakkernerd
shakkernerd merged commit 2f64ce1 into openclaw:main Jul 20, 2026
7 checks passed
@shakkernerd

Copy link
Copy Markdown
Member

Merged using rebase.

What changed after review:

  • Aggregated reaction summaries across message reads and returned authoritative state from mutations.
  • Synchronized reactions across timelines, threads, and embeds, including cursor-ordered realtime/mutation reconciliation.
  • Added concurrency, persistence, recovery, pagination, accessibility, SDK, OpenAPI, and documentation coverage.

Verification:

  • Rebased/refreshed onto current origin/main before pushing.
  • Crabbox/Testbox: no lease was created because coordinator authentication failed before execution; exact-head GitHub CI provided the landing proof.
  • Playwright E2E covered reaction persistence, realtime updates, delayed responses, ambiguous failures, threads, and embeds.
  • Auto-review: accepted findings resolved; final delta manually audited.
  • GitHub checks: Go, TypeScript, Playwright E2E, Docker Image, Linux Desktop, macOS Desktop, and Windows Desktop green.

Landed on main:

Thanks @jjjhenriksen!

@jjjhenriksen
jjjhenriksen deleted the codex/message-reactions-submit branch July 20, 2026 21:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

merge-risk: 🚨 compatibility 🚨 Merging this PR could break existing users, config, migrations, defaults, or upgrades. P2 Normal priority bug or improvement with limited blast radius. proof: 📸 screenshot Contributor real behavior proof includes screenshot evidence. proof: sufficient Contributor real behavior proof is sufficient. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants